using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace AspNetCoreExtension
{
    /// <summary>
    /// HTTP请求跟踪中间件
    /// </summary>
    public class TraceIdMiddleware
    {
        private readonly TraceIdMiddlewareOptions _options;
        private readonly RequestDelegate _next;

        private readonly ILogger _logger;
        public const string HttpContextTraceIdKey = TraceIdMiddlewareOptions.DefaultHeader;

        /// <summary>
        /// Initializes a new instance of the <see cref="TraceIdMiddleware"/> class.
        /// </summary>
        /// <param name="next">下一个中间件</param>
        /// <param name="logger">日志实例</param>
        /// <param name="options">HTTP请求跟踪中间件配置选项</param>
        public TraceIdMiddleware(RequestDelegate next, ILogger<TraceIdMiddleware> logger, TraceIdMiddlewareOptions options)
        {
            _next = next;
            _options = options;
            _logger = logger;
        }

        /// <summary>
        /// 中间件主要处理逻辑
        /// </summary>
        /// <param name="context">当前HTTP请求上下文环境</param>
        public async Task Invoke(HttpContext context)
        {
            string traceId = null;
            var traceIdFromRequest = false;
            if (context.Request.Headers.TryGetValue(_options.HeaderName, out var traceIdHeaderVal))
            {
                traceIdFromRequest = true;
                traceId = traceIdHeaderVal.ToString();
            }
            else
            {
                traceId = Guid.NewGuid().ToString("D");
            }

            if (_options.LogTrace)
                _logger.LogDebug($"请求 TraceId 为 {traceId} （{(traceIdFromRequest ? "已指定" : "新生成")}）");
            using (_logger.BeginScope($"TraceId:{traceId}"))
            {
                context.Items[HttpContextTraceIdKey] = traceId;
                context.Response.Headers.Add(_options.HeaderName, traceId);
                //using (LogContext.PushProperty("TraceId", traceId)) //Serilog日志上下文
                //{
                await AwaitHttpPipeline(context);
                //}
            }
        }

        private async Task AwaitHttpPipeline(HttpContext context)
        {
            var url = context.Request.GetDisplayUrl();
            if (_options.LogTrace)
                _logger.LogInformation("请求已开始：{method} {path}", context.Request.Method, ShortenUrlPath(url));

            var startTimestamp = Stopwatch.GetTimestamp();

            await _next(context);

            var stopTimestamp = Stopwatch.GetTimestamp();
            var elapsed = new TimeSpan((long)(TimestampToTicks * (stopTimestamp - startTimestamp)));
            if (_options.LogTrace)
                _logger.LogInformation("请求已完成：{time}ms {code} {type}", elapsed.TotalMilliseconds, context.Response.StatusCode, context.Response.ContentType);
        }

        private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;

        private static string ShortenUrlPath(string url)
        {
            const int maxUrlLength = 4096;
            if (url.Length > maxUrlLength)
                url = url.Substring(0, maxUrlLength) + "...(shortened)";
            return url;
        }

        /// <summary>
        /// 从HTTP请求上下文中判断是否启用了跟踪
        /// </summary>
        /// <param name="httpContext">当前HTTP请求上下文环境</param>
        /// <returns>
        ///   <c>true</c> if [is enabled on request] [the specified HTTP context]; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsEnabledOnRequest(HttpContext httpContext)
        {
            return httpContext?.Items[HttpContextTraceIdKey] != null;
        }

        /// <summary>
        /// 从HTTP请求上下文中获取跟踪ID
        /// </summary>
        /// <param name="httpContext">当前HTTP请求上下文环境</param>
        /// <returns></returns>
        public static string GetTraceId(HttpContext httpContext)
        {
            if (IsEnabledOnRequest(httpContext))
                return (string)httpContext.Items[HttpContextTraceIdKey];
            return null;
        }
    }

    /// <summary>
    /// 跟踪ID扩张方法
    /// </summary>
    public static class TraceIdMiddlewareExtensions
    {
        /// <summary>
        /// 配置应用Http请求跟踪
        /// </summary>
        /// <param name="appBuilder">The application builder.</param>
        /// <param name="options">The options.</param>
        public static void UseTracingId(this IApplicationBuilder appBuilder, TraceIdMiddlewareOptions options = null)
        {
            appBuilder.UseMiddleware<TraceIdMiddleware>(options ?? new TraceIdMiddlewareOptions());
        }
    }

    /// <summary>
    /// Http请求跟踪选项
    /// </summary>
    public class TraceIdMiddlewareOptions
    {
        public const string DefaultHeader = "X-Trace-Id";

        /// <summary>
        /// Initializes a new instance of the <see cref="TraceIdMiddlewareOptions"/> class.
        /// </summary>
        public TraceIdMiddlewareOptions()
        {
            HeaderName = DefaultHeader;
        }

        /// <summary>
        /// 跟踪的HTTP头定义
        /// </summary>
        public string HeaderName { get; set; }


        /// <summary>
        /// 是否输出跟踪日志（默认不输出)
        /// </summary>
        public bool LogTrace { get; set; } = false;
    }
}
